package com.rio.helper;

import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;

import com.rio.core.L;
import com.rio.core.U;
import com.rio.helper.file.FileModifiedComparator;

import android.graphics.Bitmap;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import android.webkit.WebView;

public class ViewFetcher {

	private static Class<?> windowManager;
	private String windowManagerString;
	/**
	 * The timeout length of the get, is, set, assert, enter and click methods.
	 * Default length is 10 000 milliseconds.
	 */
	public int timeout_small = 10000;
	private HandlerThread screenShotSaverThread;
	private ScreenShotSaver screenShotSaver;

	private File mImageFile;

	private ViewFetcher(File file) {
		mImageFile = file;
		setWindowManagerString();
	}
		
	public static ViewFetcher newInstance(File file){
		return new ViewFetcher(file);
	}

	/**
	 * Sets the window manager string.
	 */
	private void setWindowManagerString() {

		if (android.os.Build.VERSION.SDK_INT >= 17) {
			windowManagerString = "sDefaultWindowManager";

		} else if (android.os.Build.VERSION.SDK_INT >= 13) {
			windowManagerString = "sWindowManager";

		} else {
			windowManagerString = "mWindowManager";
		}
	}

	static {
		try {
			String windowManagerClassName;
			if (android.os.Build.VERSION.SDK_INT >= 17) {
				windowManagerClassName = "android.view.WindowManagerGlobal";
			} else {
				windowManagerClassName = "android.view.WindowManagerImpl";
			}
			windowManager = Class.forName(windowManagerClassName);

		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		} catch (SecurityException e) {
			e.printStackTrace();
		}
	}

	public View[] getWindowDecorViews() {

		Field viewsField;
		Field instanceField;
		try {
			viewsField = windowManager.getDeclaredField("mViews");
			instanceField = windowManager.getDeclaredField(windowManagerString);
			viewsField.setAccessible(true);
			instanceField.setAccessible(true);
			Object instance = instanceField.get(null);
			View[] result;
			if (android.os.Build.VERSION.SDK_INT >= 19) {
				result = ((ArrayList<View>) viewsField.get(instance))
						.toArray(new View[0]);
			} else {
				result = (View[]) viewsField.get(instance);
			}
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Returns the most recent DecorView
	 * 
	 * @param views
	 *            the views to check
	 * @return the most recent DecorView
	 */

	public final View getRecentDecorView(View[] views) {
		if (views == null)
			return null;

		final View[] decorViews = new View[views.length];
		int i = 0;
		View view;

		for (int j = 0; j < views.length; j++) {
			view = views[j];
			if (view != null
					&& view.getClass()
							.getName()
							.equals("com.android.internal.policy.impl.PhoneWindow$DecorView")) {
				decorViews[i] = view;
				i++;
			}
		}
		return getRecentContainer(decorViews);
	}

	/**
	 * Returns the most recent view container
	 * 
	 * @param views
	 *            the views to check
	 * @return the most recent view container
	 */

	private final View getRecentContainer(View[] views) {
		View container = null;
		long drawingTime = 0;
		View view;

		for (int i = 0; i < views.length; i++) {
			view = views[i];
			if (view != null && view.isShown() && view.hasWindowFocus()
					&& view.getDrawingTime() > drawingTime) {
				container = view;
				drawingTime = view.getDrawingTime();
			}
		}
		return container;
	}

	/**
	 * Takes a screenshot and saves it in the {@link Config} objects save path.
	 * Requires write permission (android.permission.WRITE_EXTERNAL_STORAGE) in
	 * AndroidManifest.xml of the application under test.
	 * 
	 * @param view
	 *            the view to take screenshot of
	 * @param name
	 *            the name to give the screenshot image
	 * @param quality
	 *            the compression rate. From 0 (compress for lowest size) to 100
	 *            (compress for maximum quality).
	 * @throws InterruptedException
	 */
	public void takeScreenshot(final String name, final int quality)
			throws InterruptedException {
		View decorView = getScreenshotView();
		if (decorView == null)
			return;

		initScreenShotSaver();
		ScreenshotRunnable runnable = new ScreenshotRunnable(decorView, name,
				quality);
		runnable.run();
	}

	/**
	 * Gets the proper view to use for a screenshot.
	 * 
	 * @throws InterruptedException
	 */
	private View getScreenshotView() throws InterruptedException {
		View decorView = getRecentDecorView(getWindowDecorViews());
		final long endTime = SystemClock.uptimeMillis() + timeout_small;

		while (decorView == null) {

			final boolean timedOut = SystemClock.uptimeMillis() > endTime;

			if (timedOut) {
				return null;
			}
			Thread.sleep(500);
			decorView = getRecentDecorView(getWindowDecorViews());
		}
		// wrapAllGLViews(decorView);

		return decorView;
	}

	/**
	 * This method initializes the aysnc screenshot saving logic
	 */
	private void initScreenShotSaver() {
		if (screenShotSaverThread == null || screenShotSaver == null) {
			screenShotSaverThread = new HandlerThread("ScreenShotSaver");
			screenShotSaverThread.start();
			screenShotSaver = new ScreenShotSaver(screenShotSaverThread);
		}
	}

	/**
	 * This class is a Handler which deals with saving the screenshots on a
	 * separate thread.
	 * 
	 * The screenshot logic by necessity has to run on the ui thread. However,
	 * in practice it seems that saving a screenshot (with quality 100) takes
	 * approx twice as long as taking it in the first place.
	 * 
	 * Saving the screenshots in a separate thread like this will thus make the
	 * screenshot process approx 3x faster as far as the main thread is
	 * concerned.
	 * 
	 */
	private class ScreenShotSaver extends Handler {
		public ScreenShotSaver(HandlerThread thread) {
			super(thread.getLooper());
		}

		/**
		 * This method posts a Bitmap with meta-data to the Handler queue.
		 * 
		 * @param bitmap
		 *            the bitmap to save
		 * @param name
		 *            the name of the file
		 * @param quality
		 *            the compression rate. From 0 (compress for lowest size) to
		 *            100 (compress for maximum quality).
		 */
		public void saveBitmap(Bitmap bitmap, String name, int quality) {
			Message message = this.obtainMessage();
			message.arg1 = quality;
			message.obj = bitmap;
			message.getData().putString("name", name);
			this.sendMessage(message);
		}

		/**
		 * Here we process the Handler queue and save the bitmaps.
		 * 
		 * @param message
		 *            A Message containing the bitmap to save, and some
		 *            metadata.
		 */
		public void handleMessage(Message message) {
			String name = message.getData().getString("name");
			int quality = message.arg1;
			Bitmap b = (Bitmap) message.obj;
			if (b != null) {
				saveFile(name, b, quality);
				b.recycle();
			} else {
				L.i("NULL BITMAP!!");
			}
		}

		/**
		 * Saves a file.
		 * 
		 * @param name
		 *            the name of the file
		 * @param b
		 *            the bitmap to save
		 * @param quality
		 *            the compression rate. From 0 (compress for lowest size) to
		 *            100 (compress for maximum quality).
		 * 
		 */
		private void saveFile(String name, Bitmap b, int quality) {
			FileOutputStream fos = null;
			String fileName = name;

			// File directory = new File("/sdcard/shot");
			if (U.notNull(mImageFile)) {
				mImageFile.mkdir();

				File fileToSave = new File(mImageFile, fileName);
				try {
					fos = new FileOutputStream(fileToSave);
					if (b.compress(Bitmap.CompressFormat.JPEG, quality, fos) == true) {
						fileToSave.renameTo(new File(mImageFile,fileName+".jpg"));						
					} else {
						if (b.compress(Bitmap.CompressFormat.PNG, quality, fos) == true) {
							fileToSave.renameTo(new File(mImageFile,fileName+".png"));
						}else{
							L.i("Saves file fail");
						}
					}
					fos.flush();
					fos.close();
				} catch (Exception e) {
					L.i("Can't save the screenshot! Requires write permission (android.permission.WRITE_EXTERNAL_STORAGE) in AndroidManifest.xml of the application under test.");
					L.e(e);
				}
			}
		}
	}

	/**
	 * Here we have a Runnable which is responsible for taking the actual
	 * screenshot, and then posting the bitmap to a Handler which will save it.
	 * 
	 * This Runnable is run on the UI thread.
	 */
	private class ScreenshotRunnable implements Runnable {

		private View view;
		private String name;
		private int quality;

		public ScreenshotRunnable(final View _view, final String _name,
				final int _quality) {
			view = _view;
			name = _name;
			quality = _quality;
		}

		public void run() {
			if (view != null) {
				Bitmap b;

				b = getBitmapOfView(view);

				if (b != null)
					screenShotSaver.saveBitmap(b, name, quality);
				else
					L.i("NULL BITMAP!!");
			}
		}
	}

	/**
	 * Returns a bitmap of a given View.
	 * 
	 * @param view
	 *            the view to save a bitmap from
	 * @return a bitmap of the given view
	 * 
	 */

	private Bitmap getBitmapOfView(final View view) {
		view.destroyDrawingCache();
		view.buildDrawingCache(false);
		Bitmap orig = view.getDrawingCache();
		Bitmap.Config config = null;

		if (orig == null) {
			return null;
		}

		config = orig.getConfig();

		if (config == null) {
			config = Bitmap.Config.ARGB_8888;
		}
		Bitmap b = orig.copy(config, false);
		view.destroyDrawingCache();
		return b;
	}
}
